﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Net.NetworkInformation;

namespace Engine.NetClasses
{
    public class Client
    {
        const int buferSize = 2048;
        List<PacketsBilder> fromserver = new List<PacketsBilder>(0);
        List<byte[]> forsend = new List<byte[]>(0);
        object locer = new object();
        bool ServerInitConect = false;
        bool _connected = false;
        uint ssesionID = 0;
        byte[] buffer;
        bool nowRecive = false;
        ManualResetEvent clientDone = new ManualResetEvent(false);
        Socket sock;
        SocketAsyncEventArgs socketEventArg;
        string[] comand;
        public bool Connected
        {
            get
            {
                return _connected;
            }
        }
        public Client(string ip, string port)
        {
            comand = new string[2];
            comand[0] = ip;
            comand[1] = port;
        }
        public Client(string ip, int port)
        {
            comand = new string[2];
            comand[0] = ip;
            comand[1] = port.ToString();
        }
        public void SetIPandPort(string ip, string port)
        {
            comand = new string[2];
            comand[0] = ip;
            comand[1] = port;
        }
        public void Start()
        {
            NetPacket.NetPacketBild();
            string[] args;
            IPAddress destinationAddr = null;          // IP Address of server to connect to
            int destinationPort = 0;                   // Port number of server
            socketEventArg = new SocketAsyncEventArgs();
            args = comand;

            if (args.Length != 2)
            {
                Console.WriteLine("Usage: AsyncSocketClient.exe <destination IP address> <destination port number>");
            }
            try
            {
                destinationAddr = IPAddress.Parse(args[0]);
                destinationPort = int.Parse(args[1]);
                if (destinationPort <= 0)
                {
                    throw new ArgumentException("Destination port number provided cannot be less than or equal to 0");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine("Usage: AsyncSocketClient.exe <destination IP address> <destination port number>");
            }

            // Create a socket and connect to the server
            sock = new Socket(destinationAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);

            socketEventArg.RemoteEndPoint = new IPEndPoint(destinationAddr, destinationPort);
            socketEventArg.UserToken = sock;

            sock.ConnectAsync(socketEventArg);
          
            clientDone.WaitOne();
            _connected = sock.Connected;
        }
        public void ShutDown()
        {
            // Data has now been sent and received from the server. Disconnect from the server
            // Socket sock = e.UserToken as Socket;
            if (sock != null || _connected == true)
            {


                if (sock.Connected)
                {
                    sock.Shutdown(SocketShutdown.Send);
                    sock.Close();
                }
            }
            //clientDone.Set();
        }
        /// <summary>
        /// A single callback is used for all socket operations. This method forwards execution on to the correct handler 
        /// based on the type of completed operation
        /// </summary>
        void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect(e);
                    _connected = sock.Connected;
                    break;
                case SocketAsyncOperation.Receive:
                    nowRecive = false;
                    _connected = sock.Connected;
                    ProcessReceive(e);
                    
                    break;
                case SocketAsyncOperation.Send:
                    _connected = sock.Connected;
                    ProcessSend(e);
                    
                    break;
                case SocketAsyncOperation.Disconnect:
                    ServerInitConect = false;
                    _connected = false;
                    closeSocket(e);
                    break;
                
                default:
                    throw new Exception("Invalid operation completed");
            }
        }
        /// <summary>
        /// Called when a ConnectAsync operation completes
        /// </summary>
        private void ProcessConnect(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success )
            {
                Console.WriteLine("Successfully connected to the server");
                buffer = new byte[1024 * 1024 * 4];
                e.SetBuffer(buffer, 0, buffer.Length);

                clientDone.Set();
                //Read data sent from the server
                //Read data sent from the server

            }
            else
            {
                throw new SocketException((int)e.SocketError);
            }
        }
        public void Send(byte[] data)
        {
            // Send 'Hello World' to the server

            //e.UserToken = sock;
            lock (forsend)
            {
                forsend.Add(data);
            }
            // e.SocketError
            //Socket sock = e.UserToken as Socket;

        }

        private void SendAsyn(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && sock.Connected)
            {
                bool willRaiseEvent = sock.SendAsync(e);
                if (!willRaiseEvent)
                {

                    ProcessSend(e);
                }
            }
        }
        public PacketsBilder[] Read()
        {
            PacketsBilder[] tmp;
            lock (locer)
            {
                tmp = fromserver.ToArray();
                fromserver.Clear();
            }
            return tmp;
        }
        void DoPacket(PacketsBilder pb)
        {
            if (_connected)
            {
                
           
            if (pb.Header._ptype == 1 && !ServerInitConect)
            {

                lock (locer)
                {
                    fromserver.Add
                    (
                     pb
                    );

                }
                Init p = pb.getStryct();
                if (p.Protocol == 713)
                {
                    ssesionID = p.ID;
                    ServerInitConect = true;
                }


            }
            else if (pb.Header._ptype == 5 && ServerInitConect)
            {
                lock (locer)
                {
                    fromserver.Add
                    (
                     pb
                    );

                }
                Ping pingSender = new Ping();
                PingOptions options = new PingOptions();

                // Use the default Ttl value which is 128,
                // but change the fragmentation behavior.
                options.DontFragment = true;

                // Create a buffer of 32 bytes of data to be transmitted.
                string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
                byte[] buffer = Encoding.ASCII.GetBytes(data);
                int timeout = 120;
                PingReply reply = pingSender.Send(comand[0], timeout, buffer, options);
                if (reply.Status == IPStatus.Success)
                {
                    NetPing pingPack = pb.getStryct();
                    pingPack.Ping =(int)reply.RoundtripTime;
                   // Random rnd = new Random(Environment.TickCount);
                    //симуляция задержки
                   // pingPack.Ping = rnd.Next(0, 5001);

                    Send(new PacketsBilder(pingPack).getPacket());
                }
            }
            else if(ServerInitConect)
            {
                lock (locer)
                {
                    fromserver.Add
                    (
                     pb
                    );
                    
                }
            } 
            }
        }
        /// <summary>
        /// Called when a ReceiveAsync operation completes
        /// </summary>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred>0 && e.SocketError == SocketError.Success && sock.Connected)
            {
                PacketsBilder pb = new PacketsBilder(e.Buffer, e.Offset);

                if (pb.Header._ptype != 0)
                {
                    DoPacket(pb);
                }
                else
                {
                    _connected = e.ConnectSocket.Connected;
                    ServerInitConect = e.ConnectSocket.Connected;
                    
                }
                
                //Read data sent from the server
                //Socket sock = e.UserToken as Socket;
                //bool willRaiseEvent = sock.ReceiveAsync(e);
                //if (!willRaiseEvent)
                //{
                //    ProcessReceive(e);
                //}
                //nowRecive = false;
            }

            else
            {
                _connected = false;
                ServerInitConect = false;
                closeSocket(e);
               
            }
        }
        void closeSocket(SocketAsyncEventArgs e)
        {
            try
            {
                sock.Shutdown(SocketShutdown.Send);
            }
            catch (Exception)
            {

            }
            sock.Close();
        }
        /// <summary>
        /// Called when a SendAsync operation completes
        /// </summary>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                Console.WriteLine("Sent 'Hello World' to the server");

                //Read data sent from the server
                //Socket sock = e.UserToken as Socket;


            }
            else
            {
                _connected = false;
                ServerInitConect = false;
                closeSocket(e);
            }
        }

        public void Update()
        {
            if (sock!=null)
            {
                _connected = sock.Connected;
                if (!_connected)
                {
                    ServerInitConect = false;
                }
            }
            if (_connected)
            {
                byte[][] tmp = null;
                lock (forsend)
                {


                    tmp = forsend.ToArray();
                    forsend.Clear();
                }



                
                for (int i = 0; i < tmp.Length; i++)
                {
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                e.UserToken = socketEventArg.UserToken;
                 e.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
                    byte[] buffer = tmp[i];
                    // Array.Copy(tmp[i], 0, buffer, e.Offset, tmp[i].Length);
                    e.SetBuffer(tmp[i], 0, tmp[i].Length);
                    SendAsyn(e);
                }
            }
            if (_connected && !nowRecive)
            {

                nowRecive = true;
                SocketAsyncEventArgs e = socketEventArg;
               // e = socketEventArg;
                bool willRaiseEvent = sock.ReceiveAsync(e);

                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
        }
    }
}
